'use strict';

var DU = require('../../../utils/DOMUtils.js').DOMUtils;
var Util = require('../../../utils/Util.js').Util;

/* ------------------------------------------------------------------------
 * If templates dont close their table tags properly or if our parser cannot
 * correctly parse certain templates, we still want to be able to round-trip
 * that content correctly.
 *
 * Ex: The Navbox template creates a <td> tag by constructing the tag string
 * using multiple parser functions -- so, the tokenizer never sees the td tag.
 * As a result, the template generated td tag is parsed as a series of strings.
 * In effect, this leads to an unbalanced </td> tag from the Navbox template.
 *
 * So, when the output of the Navbox template is itself inserted into a table,
 * the unbalanced tag output from Navbox ends up closing the container table.
 *
 * To counteract this and still enable us to roundtrip content from these
 * templates, we are going to patch the DOM so that the entire outer table is
 * wrapped as an atomic object for the purposes of visual editing (protected
 * and uneditable) and round-tripping (roundtrips as an atomic object without
 * regards to the embedded unbalanced/badly-parsed content).
 *
 * The algorithm in brief is reasonably simple.
 * - We walk the DOM bottom-up, right-to-left.
 *
 * - Whenever we encounter a <meta data-etag="table" typeof="mw:EndTag" />,
 *   i.e. a marker meta tag for a </table> tag, we walk backwards (skipping
 *   template-generated content in blocks) till we find another table/tr/td
 *   opening or closing tag.  Such a tag tells us that we have hit the
 *   end of a table (normal/common case when tags are well-balanced), or
 *   that we have hit an unstripped table tag.  We then expand the range of
 *   the farthest template till it wraps the table-end-tag marker we started
 *   off with.  This effectively lets us pretend that all intermediate
 *   content was generated by a single template and protects it as a single
 *   block.
 *
 * - The template encapsulation code pass later on further expands the range
 *   of the template so it spans an entire dom subtree (which in most cases
 *   means the outermost table will protected).  This is no different from
 *   how it handles all other templates.
 * ------------------------------------------------------------------------ */

function foundMatch(currTpl, nodeName) {
	// Did we hit a table (table/th/td/tr/tbody/th) tag
	// that is outside a template?
	var insideTpl = currTpl && !currTpl.start;
	if (!insideTpl &&
			['tr', 'td', 'th', 'tbody', 'table'].indexOf(nodeName) !== -1) {
		if (currTpl) {
			currTpl.isCulprit = true;
		}
		return true;
	}
	return false;
}

// This walks the DOM right-to-left and tries to find a table-tag
// (opening or closing tag) that is outside a template.  It keeps
// track of the currently open template and checks for a table-tag
// match only when outside it.  When a match is found, it returns
// the most recently closed template and returns it all the way up.
function findProblemTemplate(node, currTpl) {
	while (node !== null) {
		if (DU.isElt(node)) {
			var nTypeOf = node.getAttribute("typeof");
			if (DU.isTplMetaType(nTypeOf)) {
				var insideTpl = currTpl && !currTpl.start;
				if (insideTpl && node.getAttribute("about") === currTpl.tplId) {
					currTpl.start = node;
					// console.warn("---> TPL <start>: " + currTpl.tplId);
				} else if (!insideTpl && nTypeOf.match(/End/)) {
					currTpl = { end: node, tplId: node.getAttribute("about") || "" };
					// console.warn("---> TPL <end>: " + currTpl.tplId);
				}
			}
			var nodeName = node.nodeName.toLowerCase();
			// Test for "end"-tag before processing subtree
			if (foundMatch(currTpl, nodeName)) {
				return currTpl;
			}
			// Process subtree
			if (node.lastChild) {
				currTpl = findProblemTemplate(node.lastChild, currTpl);
				if (currTpl && currTpl.isCulprit) {
					// We got what we wanted -- get out of here.
					return currTpl;
				}
			}
			// Test for "start"-tag after processing subtree
			if (foundMatch(currTpl, nodeName)) {
				return currTpl;
			}
		}
		// Move left
		node = node.previousSibling;
	}
	return currTpl;
}

function handleUnbalancedTables(node, env, options, tplIdToSkip) {
	// special case for top-level
	if (DU.hasNodeName(node, "#document")) {
		node = node.body;
	}

	var c = node.lastChild;
	while (c) {
		if (tplIdToSkip && DU.isTplMarkerMeta(c) && (c.getAttribute("about") === tplIdToSkip)) {
			// Check if we hit the opening tag of the tpl/extension we are ignoring
			tplIdToSkip = null;
		} else if (DU.isMarkerMeta(c, "mw:EndTag") && c.getAttribute("data-etag") === "table") {
			// We have a stray table-end-tag marker -- this signals a problem either with
			// wikitext or with our parsed DOM.  In either case, we do the following:
			// * Find the farthest template beyond which there is a top-level table tag
			// * Artifically expand that template's range to cover this missing end tag.
			// This effectively papers over the problem by treating the entire content as
			// output of that farthest template and protects it from being edited.

			// console.warn("---- found table etag: " + c.outerHTML);
			var problemTpl = findProblemTemplate(c.previousSibling, null);
			if (problemTpl && problemTpl.isCulprit) {
				// console.warn("---- found problem tpl: " + problemTpl.tplId);
				// Move that template's end-tag after c
				c.parentNode.insertBefore(problemTpl.end, c.nextSibling);

				// Update TSR
				DU.getDataParsoid(problemTpl.end).tsr = Util.clone(DU.getDataParsoid(c).tsr);

				// Skip all nodes till we find the opening id of this template
				// FIXME: Ugh!  Duplicate tree traversal
				tplIdToSkip = problemTpl.tplId;
			}
		} else if (DU.isElt(c)) {
			// Look at c's subtree
			handleUnbalancedTables(c, env, options, tplIdToSkip);
		}

		c = c.previousSibling;
	}
}

if (typeof module === "object") {
	module.exports.handleUnbalancedTables = handleUnbalancedTables;
}
